简介

image-20240903085826547

image-20240903090159168

比如说前端发起了多个请求,需求是按照顺序返回结果,比如说1,2,3,4,5。但我们都知道,ajax请求是异步的,不能确保返回结果的顺序,有可能返回的是1,3,2,5,4。这样就不满足我们的需求了。我可以怎么做呢?可以使用 async...await 按照顺序请求,或者Promise.all来请求。那么还有一种解决方案,就是rxjs。

async...await 是前端比较熟悉的处理方式,但是到了nestjs里面,使用async...await就不太合适了?为什么呢?因为同一段时间里面的数据量不同,前端里面1s内可能要求处理5个顺序请求,写成async...await没有问题,但是后端的1s里面,可能接收到几百上千个请求,请问这时候怎么做?一个一个把async...await写出来吗?肯定是不行的。所以就有了rxjs。

比如说我在nestjs里面看到的这段代码,曾经最让我难以想象的:统一处理返回结果的这段代码,response.interceptor.ts。

这段代码我初看很难理解,但是现在有了一点眉目了,我的理解:

我创建了一个ResponseInterceptor,它是实现了NestInterceptor的,实现了intercept这个方法。那为什么要实现而不是直接重新写一个呢?这是因为NestInterceptor本身提供了很多方法,特别是这里用到的intercept方法,我只需要实现intercept的逻辑,就可以实现异步顺序处理数据。

intercept有两个主要参数,context和next,context我很熟悉,就是上下文,但是这里的next,我是怎么都想不通,为什么这里是next参数,它是怎么来的。

在 RxJS 中,Observable 是核心概念,它代表了一个可以发送多个值的流。所以interceptor方法就是创建了一个可以发送多个值的流。

next.handle() 调用下一个拦截器或控制器方法,并返回一个 Observable 对象。应该怎么理解?

在 NestJS 的拦截器机制里,next.handle() 是一个关键的调用,它用于控制请求处理流程的推进,并且返回一个 Observable 对象,下面从几个方面详细解释:

拦截器的工作原理

 

在 NestJS 中,拦截器是一种特殊的组件,它可以在请求到达控制器方法之前或之后执行一些额外的逻辑。拦截器可以用于日志记录、性能监控、响应转换等多种场景。拦截器的工作流程可以简单描述为:请求进入应用程序 -> 经过一系列拦截器 -> 到达控制器方法 -> 控制器方法处理请求并返回响应 -> 响应再经过一系列拦截器 -> 返回给客户端。

next.handle() 的作用

 

next.handle()CallHandler 实例的一个方法,它的主要作用是将请求传递给下一个处理程序。这个处理程序可以是下一个拦截器,也可以是最终的控制器方法,具体取决于当前拦截器在拦截器链中的位置。

返回 Observable 对象

 

next.handle() 返回一个 Observable 对象,这是因为 NestJS 采用了 RxJS 来处理异步操作。Observable 是 RxJS 中的一个核心概念,它表示一个可观察的数据流,可以用来处理异步事件序列。

代码示例说明

 

以下是一个简单的示例,帮助你更好地理解 next.handle() 的作用:

在这个示例中:

总结

next.handle() 是 NestJS 拦截器中用于控制请求处理流程的关键方法,它将请求传递给下一个处理程序,并返回一个 Observable 对象,允许你使用 RxJS 的操作符对请求和响应进行进一步的处理。通过这种方式,你可以在请求处理的前后执行自定义的逻辑。

pipe()方法是干什么的?

在 RxJS(Reactive Extensions for JavaScript)中,pipe() 方法是一个非常重要的方法,它用于组合多个操作符(operators),以便对 Observable 发出的数据进行链式处理。下面详细介绍 pipe() 方法的作用、用法和示例。

作用

Observable 代表一个可观察的数据流,而操作符则是用于处理这个数据流的工具。pipe() 方法允许你将多个操作符连接在一起,形成一个操作管道,数据会依次通过这些操作符进行处理,每个操作符都会对数据进行特定的转换或操作。这样可以使代码更加模块化、可维护,并且能够清晰地表达数据处理的逻辑。

用法

pipe() 方法接受任意数量的操作符作为参数,这些操作符会按照传递的顺序依次对 Observable 发出的数据进行处理。其基本语法如下:

这里的 observable 是一个 Observable 实例,operator1operator2 等是 RxJS 提供的各种操作符,例如 mapfiltertap 等。

常见操作符及示例

以下是一些常见的操作符及其在 pipe() 方法中的使用示例:

1. map 操作符

map 操作符用于对 Observable 发出的每个值进行转换。

2. filter 操作符

filter 操作符用于过滤 Observable 发出的值,只允许满足特定条件的值通过。

3. 组合多个操作符

可以在 pipe() 方法中组合多个操作符,按照顺序依次对数据进行处理。

在 NestJS 拦截器中的应用

在你之前提供的 NestJS 拦截器代码中,pipe() 方法用于对 next.handle() 返回的 Observable 进行处理:

这里使用 map 操作符对 Observable 发出的响应数据进行转换,将其封装成统一的格式。

综上所述,pipe() 方法是 RxJS 中用于组合操作符、处理 Observable 数据流的重要工具,它使得数据处理逻辑更加清晰和模块化。

 

rxjs时创建了数据流,这种“流”的概念,我想我需要不断的体会。

我最难体会的就是订阅操作,照我的想法,订阅应该是把整个数据流拿到了,但是这种相反是错误的,不是吗?

既然是“拿到了”,那么之后别人想订阅这个数据流,该向谁订阅呢?是不是需要向拿到了这个数据流的对象来订阅,这样搞就非常复杂了,就没有了“流”的概念了。

所以针对这个数据流的概念,我问了一下:怎么样理解rxjs里面的数据流?

答:

在 RxJS(Reactive Extensions for JavaScript)里,数据流可理解为随时间产生的值的序列,就像水流一样持续流动并携带信息。以下从几个方面帮助你理解 RxJS 中的数据流:

1. 数据流的产生

2. 数据流的流动

3. 数据流的处理

4. 数据流的结束

综上所述,RxJS 中的数据流是一种强大的抽象概念,它允许你以声明式的方式处理异步数据,使得代码更加简洁、可维护和可测试。

 

 

image-20240903090651344

image-20240903090756122

从事件这个概念提供的案例可以看到,前端、后端都可以使用rxjs。

image-20240903091558264

这里有一个重要的概念:Schedulers。这个概念解决了我心中的一个疑惑:nestjs中为什么需要rxjs?

在学习nestjs的过程中,我学习了拦截器、控制器、过滤器、装饰器这些概念,由此引出了AOP的概念。但是为什么能够实现AOP?这个我是不懂的。那么rxjs中的Schedulers就解决了这个问题,是通过调度器来控制事件的并发情况的。

image-20240903092225960

image-20240903092306128

image-20240903092835056

直接在https://rxjs.dev的F12里面运行已经不行了,它提示这样:

image-20240903093653434

在这里来测试代码:https://stackblitz.com/edit/rxjs

上面的代码应该写成这样:

效果:

image-20240903094026451

image-20240903094212138

image-20240903094437976

这里我获取了一个好消息,最常见的rxjs的API,都从rxjs或者rxjs/operators引入,红框中的四种位置都是蛮好记的。

image-20240903095253389

下面是一个例子:

image-20240903100225387

上面的代码,输出结果之后和老师的不一样:

image-20240903101459077

也不知道是怎么回事。这是在stackblitz里面测试的,我直接在html文件里面测试,看会怎么样?

查看效果是正常的:

上面这张图很重要,差不多就是rxjs的标准编写流程了。其中有一段代码很难理解:

image-20240903105340983

click$是可观察的物件,而observer是观察者物件。难道不应该是观察者物件去订阅可观察的物件吗?不应该写成这样吗?

问:为什么不是observer.subscribe(click$)这种方式来调用呢?不应该是 观察者订阅Observable的变化吗?

答:

这是一个很好的问题,涉及到 RxJS 的核心概念和设计模式。让我们详细解释为什么在 RxJS 中使用 observable.subscribe(observer) 而不是 observer.subscribe(observable)

1. Observable 和 Observer 的关系

在 RxJS 中,Observable 和 Observer 的关系类似于生产者和消费者的关系:

2. 订阅的方向

订阅机制决定了谁来控制数据流动:

3. 为何设计为 observable.subscribe(observer)

设计 RxJS 为 observable.subscribe(observer) 的方式有几个原因:

  1. 符合事件的自然流向:事件是由 Observable 发出的,所以 Observable 自然地“拥有”这个流动控制权,而 Observer 是去“订阅”这个流。将 subscribe 方法定义在 Observable 上,更符合事件从源头发出到被处理的流向。
  2. 职责分离:Observable 和 Observer 分别承担不同的职责。Observable 负责产生数据(事件),Observer 负责处理数据(反应)。这种设计保持了单一职责原则,清晰地分离了生产和消费的逻辑。
  3. 便于链式操作:RxJS 的强大之处在于能够以链式的方式操作数据流。例如,通过 observable.pipe(...).subscribe(observer),你可以在订阅之前对数据进行各种操作(例如过滤、映射、合并等)。这样的链式设计提高了代码的可读性和可维护性。

4. 举个例子

想象一个电台广播和听众的例子:

在这个比喻中,电台不会去主动寻找听众并“订阅”他们,而是听众决定去收听电台的广播。这和 observable.subscribe(observer) 的设计是类似的。

总结

通过这种设计,RxJS 保持了良好的模块化和灵活性,使得开发者能够以一种声明式和易于理解的方式处理复杂的异步数据流。

https://rxjs.dev/guide/operators

在这个地址中,有所有的operators的列表,完整的列表可以查看references page。

image-20240903101657814

image-20240903101936060

下面是一个过滤的例子:

image-20240903102013154

查看效果:

image-20240903103117988

查看效果:

注意我的鼠标,我的鼠标每移动一次位置,我都会点击一次,我总共移动了8次,为什么只输出4次呢?这四次是哪四次呢?首先需要符合filter的条件,那么前2次就没有输出,接着4次符合filter条件,并且take递增,所以有4次输出,最后2次因为不符合take条件了,所以没有输出。

在之前的例子中,我们都是定义一个观察者物件,如下图:

image-20240903122621165

但是在下面,我们将先创建一个Subject主体物件,然后再由这个主体物件去创建观察者物件并订阅。

image-20240903122450075

上面这个例子是什么意思呢?可以理解为中介者模式,这里的subject相当于一个中介者(同时是Observable和Observer),当有多个观察者来订阅Observable时,通过使用subject来订阅Observable,然后其余的观察者订阅subject。比如说多个观察者订阅x.clientX,可以让Observable只发出一次消息即可,真正的广播动作交给subject来完成,这样做的好处就是Observable可以尽可能保持简单。

弹珠图

例1:

image-20240903123608499

这张图怎么看呢?横向可以看成是时间,比如说上面这个可以看成是要或者待执行的代码(或者说数据流)的顺序,尾部的竖线表示结束。

image-20240903123745413

那么经过take(2)处理之后,尾部的竖线就到了b上面,表示只执行2个就结束了。表示真实执行的代码(或者说数据流)的顺序。

image-20240903124034140

例2:

image-20240903124152571

上面的1、2、3是待处理的数据流,经过map(x => 10 * x)处理之后,就变为了10、20、30。

例3:

image-20240903125205296

concat是用来串接两个Observable对象。a、b和x、y本来是属于不同的Observable的,但是经过concat处理之后,可以变为一个Observable。

理解rxjs的动态网站

网站一:

https://rxmarbles.com/

image-20240903125430812

这个网站太棒了,对于理解rxjs真的很有帮助。大部分的API可以通过移动元素来交互感受。

网站二:

https://reactive.how/rxjs/explorer

image-20240903130026916

rxjs Operators

image-20240903130557502

image-20240903130708847

如何选择需要的operator呢?在官网的这里可以交互式的选择:

image-20240903130819464

操作示例:

最后得到了fromEvent这个API,可以供参考。

operators分类

image-20240903131013869

image-20240903131255701

image-20240903131858232

image-20240903131938190

image-20240903132019713

image-20240903132045558

image-20240903132105894

image-20240903132321827

image-20240903132349278

image-20240903132408815

image-20240903132421422

额外话

因为rxjs有很多API的名称和js的数组或者对象的方法有重名,所以不要搞混了。比如说rxjs里面的map、concat方法,不要和js的数组的方法搞混了。既然是用在rxjs上面的,肯定就是rxjs的map、concat方法。